自定义地图服务显示 Sample详情

最后更新时间:2020年12月4日

地图服务负责给地图提供数据,MapGIS Mobile支持自定义地图服务的功能,包括自定义的矢量、瓦片地图服务。可以在地图视图中加载自定义数据、自定义切片规则的地图,具备良好的扩展性。

在实现自定义地图服务之前,需要理解地图服务的概念。地图服务,即是负责提供地图数据的服务,它为服务图层ImageLayer提供地图数据,在其中包括地图数据范围、数据类型、服务名称、地图参照系等信息。有了地图服务,才能获取地图数据进而加载显示出来。

实现方法

地图服务对应类为API程序包com.zondy.mapgis.map中的MapServer类,其子类包括矢量地图服务(VectorMapServer)和瓦片地图服务(TileMapServer),对应GIS地理数据中的两种常用类型。服务图层ImageLayer调用MapServer来获取数据,然后将ImageLayer添加到Map地图中,然后将地图赋予地图容器MapView即可显示地图。

实现自定义的地图服务,就需要自定义一个地图服务类,继承MapServer的子类VectorMapServer或者TileMapServer,然后重写实现其中的方法。

自定义矢量地图服务

自定义矢量地图服务,能够将普通的图片赋予地理空间信息,从而以地图的形式展示,可用于在基础底图上添加额外的信息。

1

自定义矢量地图服务

自定义矢量地图服务DefinedVectorMapServer,继承VectorMapServer,重写方法,最关键的方法是getVectorImage,传递图片的高、宽、要显示的地图范围,经过一系列运算,生成显示的地图数据。


    @Override
    public byte[] getVectorImage(int imgWidth, int imgHeight, double dispRectXmin, double dispRectYmin, double dispRectXmax, double dispRectYmax) {
        Log.d(TARG, "imgWidth:" + imgWidth);
        Log.d(TARG, "imgHeight:" + imgHeight);
        Log.d(TARG, "dispRectXmin:" + dispRectXmin);
        Log.d(TARG, "dispRectYmin:" + dispRectYmin);
        Log.d(TARG, "dispRectXmax:" + dispRectXmax);
        Log.d(TARG, "dispRectYmax:" + dispRectYmax);

        //根据瓦片的地理范围跟整个地理范围的比值计算出瓦片对应图片的高度
        int xMin = (int) ((dispRectXmin - entireExtent.getXMin()) / (entireExtent.getXMax() - entireExtent.getXMin()) * bitmapWidth);
        int xMax = (int) ((dispRectXmax - entireExtent.getXMin()) / (entireExtent.getXMax() - entireExtent.getXMin()) * bitmapWidth);
        int yMin = (int) ((entireExtent.getYMax() - dispRectYmax) / (entireExtent.getYMax() - entireExtent.getYMin()) * bitmapHeight);
        int yMax = (int) ((entireExtent.getYMax() - dispRectYmin) / (entireExtent.getYMax() - entireExtent.getYMin()) * bitmapHeight);


        android.graphics.Rect srcRect = new android.graphics.Rect();
        srcRect.left = xMin < 0 ? 0 : xMin;
        srcRect.top = yMin < 0 ? 0 : yMin;
        srcRect.right = xMax > bitmapWidth ? bitmapWidth : xMax;
        srcRect.bottom = yMax > bitmapHeight ? bitmapHeight : yMax;

        RectF dstRect = new RectF();
        dstRect.left = 1.0f * Math.abs(xMin - srcRect.left) / (xMax - xMin) * imgWidth;
        dstRect.top = 1.0f * Math.abs(yMin - srcRect.top) / (yMax - yMin) * imgHeight;
        dstRect.right = 1.0f * Math.abs(srcRect.right - xMin) / (xMax - xMin) * imgWidth;
        dstRect.bottom = 1.0f * Math.abs(srcRect.bottom - yMin) / (yMax - yMin) * imgHeight;

        Bitmap bitmapCanvas = Bitmap.createBitmap(imgWidth, imgHeight, Bitmap.Config.ARGB_8888);
        bitmapCanvas.eraseColor(Color.TRANSPARENT);
        android.graphics.Paint paint = new android.graphics.Paint();
        Canvas canvas = new Canvas();
        canvas.setBitmap(bitmapCanvas);
        canvas.drawBitmap(mBitMapData, srcRect, dstRect, paint);

        byte[] buffer = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bitmapCanvas.compress(CompressFormat.PNG, 100, baos);
        buffer = baos.toByteArray();
        return buffer;
    }

然后重写其他关键方法。

    //地图浏览类型
    @Override
    public MapServerBrowseType getMapBrowseType() {
        return MapServerBrowseType.MapVector;
    }

    //数据范围
    @Override
    public Rect getEntireExtent() {
        return entireExtent;
    }

    //服务名称
    @Override
    public String getName() {
        return "DefinedVectorMapServer";
    }

    //服务url
    @Override
    public String getURL() {
        return "localhost";
    }

    //初始可见性
    @Override
    public boolean getIsValid() {
        return true;
    }

    //空间参考系
    @Override
    public SRefData getSRS() {
        return SpaProjection.getWGS84();
    }

2

数据准备

准备要制作自定义矢量地图服务的图片,以及确定需要展示的地图区域。

    //构建Bitmap
    AssetManager am = getAssets();
    InputStream stream1 = am.open("vectorServer.jpg");
    Bitmap bitmapVector = BitmapFactory.decodeStream(stream1);
    //显示的坐标范围
    Rect vectorRect = new Rect(12727405.02034, 3569836.65592, 12741743.19188, 3579555.67778);

3

地图显示

实例化自定义矢量地图服务,设置信息。然后构建服务图层,最后进行显示。


     //创建自定义矢量地图服务对象,并设置数据、地图范围
     defineVectorMapServer = new DefinedVectorMapServer();
     defineVectorMapServer.setData(bitmapVector);
     defineVectorMapServer.setServerRect(vectorRect);
     //设置地图服务给服务图层
     setDefineVectorMapServer(defineVectorMapServer);

    /**
     * 添加自定义矢量地图服务
     *
     * @param mapServer
     */
    private void setDefineVectorMapServer(final MapServer mapServer) {
        mapView.stopCurRequest(new MapViewStopCurRequestCallback() {
            @Override
            public void onDidStopCurRequest() {
                new Thread(new Runnable() {

                    @Override
                    public void run() {
                        if (mServerLayerDefineVector == null) {
                            //创建服务图层对象
                            mServerLayerDefineVector = new ImageLayer();
                            //设置数据只从服务中读取
                            mServerLayerDefineVector.setAccessMode(MapServerAccessMode.ServerOnly);
                            //设置地图服务
                            mServerLayerDefineVector.setMapServer(mapServer);
                            //地图中插入图层
                            map.insert(map.getLayerCount() - 1, mServerLayerDefineVector);
                        } else {
                            mServerLayerDefineVector.deleteTileCache();
                        }
                        //刷新地图容器
                        mapView.forceRefresh();
                    }
                }).start();
            }
        });
    }

显示效果:

自定义矢量地图服务.jpg

自定义瓦片地图服务

在实现自定义瓦片地图服务之前,先了解两个知识:

地图瓦片技术:将配置好的一定坐标范围的地图,按照若干比例尺(瓦片级别)和指定的图片尺寸,在服务器端切成若干行及列的正方形图片,以指定的格式保存成图像文件,按一定的命名规则和组织方式存储到服务器的目录系统中或者数据系统中形成金字塔模型的静态图片

瓦片金字塔从底层到顶层,分辨率越来越高,但表示地理范围不变,一般根据地图服务平台的要求的最大缩放级别,把缩放级别最低。地图比例尺最大的地图图片作为金字塔的底层。

MapGIS Mobile支持由用户自定义切片规则,指定地图原点,地图范围,自定义缩放级别,并可以规定只让用户看到部分缩放级别。

1

自定义瓦片地图服务类

自定义瓦片地图服务类DefinedTileMapServer,继承TileMapServer,重新方法。

(1)重写getEntireExtent()方法,给定地图数据范围,也即是传递的自定义的地图范围。

    @Override
    public Rect getEntireExtent() {
        return dataRect;
    }

(2)重新getMinZoom()设置最小缩放级别、getMaxZoom()设置最大缩放级别、getZoomCapacity()方法,设置瓦片自身缩放能力。这些信息可自定义。

    int minZoomCapacity = 0;
    int maxZoomCapacity = 8;
    
    //最大缩放级别
    @Override
    public int getMaxZoom() {
        return maxZoomCapacity;
    }
    //最小缩放级别
    @Override
    public int getMinZoom() {
        return minZoomCapacity;
    }

    // 瓦片实际缩放级别
    public boolean getZoomCapacity(IntRef minZoom, IntRef maxZoom) {
        minZoom.setValue(minZoomCapacity);
        maxZoom.setValue(maxZoomCapacity);
        return true;
    }

(3)重写getTileResolution(),这个方法返回一个不同级别下不同分辨率的数组,计算分辨率代码如下所示:

        resolutions = new double[maxZoomCapacity - minZoomCapacity + 1];
        // 设置级别2显示原始图片大小
        resolutions[refZoom] = (dataRect.getXMax() - dataRect.getXMin()) / bitmapWidth;
        for (int i = minZoomCapacity; i < refZoom; i++) {
            resolutions[i] = resolutions[refZoom] * Math.pow(2, refZoom - i);
        }

        for (int j = refZoom + 1; j <= maxZoomCapacity; j++) {
            resolutions[j] = resolutions[refZoom] / Math.pow(2, j - refZoom);
        }

    //瓦片分辨率
    @Override
    public double getTileResolution(int zoom) {
        return resolutions[zoom];
    }

(4)重写getTileOriginXY()方法,给定瓦片原点dotOrigin,在这里地图原点设为左上角:

    // 原点
    @Override
    public Dot getTileOriginXY() {
        dotOrigin.setX(dataRect.getXMin());
        dotOrigin.setY(dataRect.getYMax());
        return dotOrigin;
    }

(5)重写getTileSize(),设置瓦片大小,一般256显示效果较好;

    @Override
    public boolean getTileSize(IntRef int1, IntRef int2) {
        int1.setValue(256);
        int2.setValue(256);
        return true;
    }

(6)重写getTileMatrix(),传递每个级别下其实行列号,计算行列号代码如下所示:

    // 瓦片显示范围
    @Override
    public boolean getTileMatrix(int zoom, IntRef topRow, IntRef leftCol, IntRef bottomRow, IntRef rightCol) {
        // 计算瓦片起始行列号
        topRow.setValue((int) Math.floor((dotOrigin.getY() - dataRect.getYMax()) / (resolutions[(int) zoom] * tileSize)));
        leftCol.setValue((int) Math.floor((dataRect.getXMin() - dotOrigin.getX()) / (resolutions[(int) zoom] * tileSize)));

        bottomRow.setValue((int) Math.floor((dotOrigin.getY() - dataRect.getYMin()) / (resolutions[(int) zoom] * tileSize)));
        rightCol.setValue((int) Math.floor((dataRect.getXMax() - dotOrigin.getX()) / (resolutions[(int) zoom] * tileSize)));
        return true;
    }

(7)重写getTileImage(long row, long col, long zoom)根据getTileMatrix()方法计算出来的行列号,在这里反算出行列号对应的地图范围,图片的地理范围包含在其中,然后根据地理范围跟dataRect的比值,计算图片尺寸,并返回当前瓦片二进制,代码如下所示:

    // 得到的瓦片
    @Override
    public byte[] getTileImage(int row, int col, int zoom) {

        // tileDataRect为根据传递行列号计算出当前矩阵行列,对应地理范围
        Rect tileDataRect = new Rect();
        double tileDataSize = resolutions[(int) zoom] * tileSize;

        tileDataRect.setXMin(dotOrigin.getX() + tileDataSize * col);
        tileDataRect.setXMax(dotOrigin.getX() + tileDataSize * (col + 1));

        tileDataRect.setYMin(dotOrigin.getY() - tileDataSize * (row + 1));
        tileDataRect.setYMax(dotOrigin.getY() - tileDataSize * row);
        // 根据瓦片外包矩形与地理范围的比例,计算出当前瓦片矩形对应图片的高宽
        int xmin = (int) ((tileDataRect.getXMin() - dataRect.getXMin()) / (dataRect.getXMax() - dataRect.getXMin()) * bitmapWidth);
        int ymin = (int) ((dataRect.getYMax() - tileDataRect.getYMax()) / (dataRect.getYMax() - dataRect.getYMin()) * bitmapHeight);
        int xmax = (int) ((tileDataRect.getXMax() - dataRect.getXMin()) / (dataRect.getXMax() - dataRect.getXMin()) * bitmapWidth);
        int ymax = (int) ((dataRect.getYMax() - tileDataRect.getYMin()) / (dataRect.getYMax() - dataRect.getYMin()) * bitmapHeight);

        if (xmax < 0 || xmin > bitmapWidth || ymax < 0 || ymin > bitmapHeight)
            return null;

        android.graphics.Rect srcRect = new android.graphics.Rect();

        srcRect.left = xmin < 0 ? 0 : xmin;
        srcRect.top = ymin < 0 ? 0 : ymin;
        srcRect.right = xmax > bitmapWidth ? bitmapWidth : xmax;
        srcRect.bottom = ymax > bitmapHeight ? bitmapHeight : ymax;

        RectF dstRect = new RectF();

        dstRect.left = 1.0f * (srcRect.left - xmin) / (xmax - xmin) * tileSize;
        dstRect.top = 1.0f * (srcRect.top - ymin) / (ymax - ymin) * tileSize;
        dstRect.right = 1.0f * (srcRect.right - xmin) / (xmax - xmin) * tileSize;
        dstRect.bottom = 1.0f * (srcRect.bottom - ymin) / (ymax - ymin) * tileSize;
        // 在当前分辨率下,根据行列号反算的比例,计算当前行列号对应图片的尺寸,并返回当前尺寸的二进制
        Bitmap bitmapCanvas = Bitmap.createBitmap(tileSize, tileSize, Bitmap.Config.ARGB_8888);
        Paint paint = new Paint();
        Canvas canvas = new Canvas();
        canvas.setBitmap(bitmapCanvas);
        canvas.drawBitmap(bitmapData, srcRect, dstRect, paint);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] bytes = null;
        bitmapCanvas.compress(Bitmap.CompressFormat.PNG, 100, baos);
        bytes = baos.toByteArray();
        return bytes;
    }

2

数据准备

准备要制作自定义瓦片地图服务的图片,以及确定需要展示的地图区域。

     //构建Bitmap
     AssetManager am = getAssets();
     InputStream stream2 = am.open("tileServer.jpg");
     bitmapTile = BitmapFactory.decodeStream(stream2);
     //瓦片地图范围
     Rect tileRect = new Rect(12719586.11, 3572675.61, 12728907.50, 3581903.60);

3

实例化自定义地图服务对象

实例化自定义瓦片地图服务对象,设置信息。然后构建服务图层,最后进行显示。

    //创建自定义瓦片地图服务对象,并设置数据、地图范围
    defineTileMapServer = new DefinedTileMapServer();
    defineTileMapServer.setData(bitmapTile);
    defineTileMapServer.setServerRect(tileRect);
    //设置地图服务给服务图层
    setDefineTileMapServer(defineTileMapServer);

    /**
     * 添加自定义瓦片地图服务
     *
     * @param mapServer
     */
    private void setDefineTileMapServer(final MapServer mapServer) {
        mapView.stopCurRequest(new MapViewStopCurRequestCallback() {
            @Override
            public void onDidStopCurRequest() {
                new Thread(new Runnable() {

                    @Override
                    public void run() {
                        if (mServerLayerDefineTile == null) {
                            //构建服务图层对象
                            mServerLayerDefineTile = new ImageLayer();
                            //设置数据只从服务中读取
                            mServerLayerDefineTile.setAccessMode(MapServerAccessMode.ServerOnly);
                            // 设置地图服务,把mapserver添加到服务图层上
                            mServerLayerDefineTile.setMapServer(mapServer);
                            // 将服务图层添加到地图上(只添加一次,避免移除服务图层的时候出错)
                            map.insert(map.getLayerCount() - 1, mServerLayerDefineTile);
                        } else {
                            mServerLayerDefineTile.deleteTileCache();
                        }
                        // 刷新地图容器,跟stopCurRequest方法配合使用
                        mapView.forceRefresh();
                    }
                }).start();
            }
        });
    }

显示效果:

自定义瓦片地图服务.jpg